{
  "nbformat": 4,
  "nbformat_minor": 0,
  "metadata": {
    "colab": {
      "name": "6.4. 循环神经网络的从零开始实现.ipynb",
      "provenance": [],
      "collapsed_sections": [],
      "include_colab_link": true
    },
    "kernelspec": {
      "name": "python3",
      "display_name": "Python 3"
    },
    "accelerator": "TPU"
  },
  "cells": [
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "view-in-github",
        "colab_type": "text"
      },
      "source": [
        "<a href=\"https://colab.research.google.com/github/chongzicbo/Dive-into-Deep-Learning-tf.keras/blob/master/6.4.%20%E5%BE%AA%E7%8E%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C%E7%9A%84%E4%BB%8E%E9%9B%B6%E5%BC%80%E5%A7%8B%E5%AE%9E%E7%8E%B0.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "bUp3_yaDtRZ9",
        "colab_type": "text"
      },
      "source": [
        "##6.4. 循环神经网络的从零开始实现\n",
        "在本节中，我们将从零开始实现一个基于字符级循环神经网络的语言模型，并在周杰伦专辑歌词数据集上训练一个模型来进行歌词创作。首先，我们读取周杰伦专辑歌词数据集："
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "tb08SRdC5l1T",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "%matplotlib inline\n",
        "import math\n",
        "import tensorflow as tf\n",
        "import numpy as np\n",
        "from IPython import display\n",
        "import matplotlib.pyplot as plt\n",
        "from tensorflow import keras\n",
        "from tensorflow.keras import losses\n",
        "from tensorflow.data import Dataset\n",
        "import time\n",
        "import random\n",
        "import zipfile"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "eNRsn1jf56zE",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "tf.enable_eager_execution()"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "jSjY63NJ5947",
        "colab_type": "code",
        "outputId": "b2b285c6-7202-46d1-9a56-c87c00ba5f91",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 121
        }
      },
      "source": [
        "def load_data_jay_lyrics():\n",
        "  from google.colab import drive\n",
        "  drive.mount('/content/drive')\n",
        "  with zipfile.ZipFile('/content/drive/My Drive/data/d2l-zh-tensoflow/jaychou_lyrics.txt.zip')as zin:\n",
        "    with zin.open('jaychou_lyrics.txt') as f:\n",
        "      corpus_chars=f.read().decode('utf-8')\n",
        "  corpus_chars=corpus_chars.replace('\\n',' ').replace('\\r',' ')\n",
        "  corpus_chars=corpus_chars[0:10000]\n",
        "  idx_to_char=list(set(corpus_chars))\n",
        "  char_to_idx=dict([(char,i) for i,char in enumerate(idx_to_char)])\n",
        "  vocab_size=len(char_to_idx)\n",
        "  corpus_indices=[char_to_idx[char] for char in corpus_chars]\n",
        "  return corpus_indices,char_to_idx,idx_to_char,vocab_size\n",
        "\n",
        "(corpus_indices,char_to_idx,idx_to_char,vocab_size)=load_data_jay_lyrics() "
      ],
      "execution_count": 0,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "Go to this URL in a browser: https://accounts.google.com/o/oauth2/auth?client_id=947318989803-6bn6qk8qdgf4n4g3pfee6491hc0brc4i.apps.googleusercontent.com&redirect_uri=urn%3aietf%3awg%3aoauth%3a2.0%3aoob&response_type=code&scope=email%20https%3a%2f%2fwww.googleapis.com%2fauth%2fdocs.test%20https%3a%2f%2fwww.googleapis.com%2fauth%2fdrive%20https%3a%2f%2fwww.googleapis.com%2fauth%2fdrive.photos.readonly%20https%3a%2f%2fwww.googleapis.com%2fauth%2fpeopleapi.readonly\n",
            "\n",
            "Enter your authorization code:\n",
            "··········\n",
            "Mounted at /content/drive\n"
          ],
          "name": "stdout"
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "85tD-YQ0tYH4",
        "colab_type": "text"
      },
      "source": [
        "###6.4.1. one-hot向量\n",
        "为了将词表示成向量输入到神经网络，一个简单的办法是使用one-hot向量。假设词典中不同字符的数量为 N （即词典大小vocab_size），每个字符已经同一个从0到 N−1 的连续整数值索引一一对应。如果一个字符的索引是整数 i , 那么我们创建一个全0的长为 N 的向量，并将其位置为 i 的元素设成1。该向量就是对原字符的one-hot向量。下面分别展示了索引为0和2的one-hot向量，向量长度等于词典大小。"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "r4HCYYMp6ONb",
        "colab_type": "code",
        "outputId": "b5a1f30b-300b-4fe2-e374-ebf7d626390d",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 67
        }
      },
      "source": [
        "tf.one_hot(indices=[0,2],depth=vocab_size)"
      ],
      "execution_count": 0,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "<tf.Tensor: id=4, shape=(2, 1027), dtype=float32, numpy=\n",
              "array([[1., 0., 0., ..., 0., 0., 0.],\n",
              "       [0., 0., 1., ..., 0., 0., 0.]], dtype=float32)>"
            ]
          },
          "metadata": {
            "tags": []
          },
          "execution_count": 4
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "plsHfDyetbnc",
        "colab_type": "text"
      },
      "source": [
        "我们每次采样的小批量的形状是(批量大小, 时间步数)。下面的函数将这样的小批量变换成数个可以输入进网络的形状为(批量大小, 词典大小)的矩阵，矩阵个数等于时间步数。也就是说，时间步 t 的输入为 $X_t \\in R_n \\times d$ ，其中 $n$ 为批量大小， d 为输入个数，即one-hot向量长度（词典大小）。"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "yoDxZoYHj4MO",
        "colab_type": "code",
        "outputId": "13caabe8-0691-4145-c32e-9adeb04d37d5",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 34
        }
      },
      "source": [
        "def to_onehot(X,size):\n",
        "  return [tf.one_hot(x,size) for x in tf.transpose(X)]\n",
        "\n",
        "X=tf.reshape(tf.range(10),(2,5))\n",
        "inputs=to_onehot(X,vocab_size)\n",
        "len(inputs),inputs[0].shape"
      ],
      "execution_count": 0,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "(5, TensorShape([Dimension(2), Dimension(1027)]))"
            ]
          },
          "metadata": {
            "tags": []
          },
          "execution_count": 5
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "iV5-opRwtsA1",
        "colab_type": "text"
      },
      "source": [
        "###6.4.2. 初始化模型参数\n",
        "接下来，我们初始化模型参数。隐藏单元个数 num_hiddens是一个超参数。"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "Oq6Xkg0flLwW",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "num_inputs,num_hiddens,num_outputs=vocab_size,256,vocab_size\n",
        "\n",
        "def get_params():\n",
        "  def _one(shape):\n",
        "    return tf.Variable(tf.random.normal(stddev=0.01,shape=shape))\n",
        "\n",
        "  #隐藏层参数\n",
        "  W_xh=_one((num_inputs,num_hiddens)) #输入和隐藏层\n",
        "  W_hh=_one((num_hiddens,num_hiddens)) #隐藏层之间\n",
        "  b_h=tf.Variable(tf.zeros(num_hiddens))\n",
        "\n",
        "  #输出层参数\n",
        "  W_hq=_one((num_hiddens,num_outputs)) #隐藏层和输出层\n",
        "  b_q=tf.Variable(tf.zeros(num_outputs))\n",
        "  params=[W_xh,W_hh,b_h,W_hq,b_q]\n",
        "  return params"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "tGERnBMSt8vq",
        "colab_type": "text"
      },
      "source": [
        "###6.4.3. 定义模型\n",
        "我们根据循环神经网络的计算表达式实现该模型。首先定义init_rnn_state函数来返回初始化的隐藏状态。它返回由一个形状为(批量大小, 隐藏单元个数)的值为0的NDArray组成的元组。使用元组是为了更便于处理隐藏状态含有多个NDArray的情况。"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "ifh5Z1ywmOSK",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "def init_rnn_state(batch_size,num_hiddens):\n",
        "  return (tf.zeros(shape=(batch_size,num_hiddens)),)"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "mCnL9kDRuAN7",
        "colab_type": "text"
      },
      "source": [
        "下面的rnn函数定义了在一个时间步里如何计算隐藏状态和输出。这里的激活函数使用了tanh函数。“多层感知机”一节中介绍过，当元素在实数域上均匀分布时，tanh函数值的均值为0。"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "i8QcIkXtmuvw",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "def rnn(inputs,state,params):\n",
        "  #inputs和outputs皆为num_steps个形状为(batch_size,vocab_size)的矩阵\n",
        "  W_xh,W_hh,b_h,W_hq,b_q=params\n",
        "  H,=state\n",
        "  outputs=[]\n",
        "  for X in inputs:\n",
        "    H=tf.tanh(tf.matmul(X,W_xh)+tf.matmul(H,W_hh)+b_h)\n",
        "    Y=tf.matmul(H,W_hq)+b_q\n",
        "    outputs.append(Y)\n",
        "\n",
        "  return outputs,(H,)"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "EwvAUZR-uHMz",
        "colab_type": "text"
      },
      "source": [
        "做个简单的测试来观察输出结果的个数（时间步数），以及第一个时间步的输出层输出的形状和隐藏状态的形状。"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "KFo7-jrknzUT",
        "colab_type": "code",
        "outputId": "9be017d0-38d6-420f-b52e-a33c296cedd7",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 67
        }
      },
      "source": [
        "state=init_rnn_state(X.shape[0],num_hiddens)\n",
        "inputs=to_onehot(X,vocab_size)\n",
        "params=get_params()\n",
        "outputs,state_new=rnn(inputs,state,params)\n",
        "len(outputs),outputs[0].shape,state_new[0].shape"
      ],
      "execution_count": 0,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "(5,\n",
              " TensorShape([Dimension(2), Dimension(1027)]),\n",
              " TensorShape([Dimension(2), Dimension(256)]))"
            ]
          },
          "metadata": {
            "tags": []
          },
          "execution_count": 9
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "QRN8NF_MuJtT",
        "colab_type": "text"
      },
      "source": [
        "###6.4.4. 定义预测函数\n",
        "以下函数基于前缀prefix（含有数个字符的字符串）来预测接下来的num_chars个字符。这个函数稍显复杂，其中我们将循环神经单元rnn设置成了函数参数，这样在后面小节介绍其他循环神经网络时能重复使用这个函数。"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "o9z2LkRgoPBf",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "def predict_rnn(prefix,num_chars,rnn,params,init_rnn_state,num_hiddens,vocab_size,idx_to_char,char_to_idx):\n",
        "  state=init_rnn_state(1,num_hiddens)\n",
        "  output=[char_to_idx[prefix[0]]]\n",
        "  for t in range(num_chars+len(prefix)-1):\n",
        "    #将上一时间步的输出作为当前时间步的输入\n",
        "    X=to_onehot(tf.reshape(tf.constant([output[-1]]),shape=(1,1)),vocab_size)\n",
        "    # print(X[0].shape)\n",
        "    #计算输出和更新隐藏状态\n",
        "    (Y,state)=rnn(X,state,params)\n",
        "    #下一个时间步的输入是prefix里的字符或者当前的最佳预测字符\n",
        "    if t<len(prefix)-1:\n",
        "      output.append(char_to_idx[prefix[t+1]])\n",
        "    else:\n",
        "      output.append(tf.argmax(Y[0],axis=1).numpy()[0])\n",
        "  return ''.join([idx_to_char[i] for i in output])"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "LIgbw5TvuRPr",
        "colab_type": "text"
      },
      "source": [
        "我们先测试一下predict_rnn函数。我们将根据前缀“分开”创作长度为10个字符（不考虑前缀长度）的一段歌词。因为模型参数为随机值，所以预测结果也是随机的。"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "x_1fIsbTr7rm",
        "colab_type": "code",
        "outputId": "4c0e16cb-eed9-4136-c44e-2a272587f50a",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 34
        }
      },
      "source": [
        "predict_rnn('分开',10,rnn,params,init_rnn_state,num_hiddens,vocab_size,idx_to_char,char_to_idx)"
      ],
      "execution_count": 0,
      "outputs": [
        {
          "output_type": "execute_result",
          "data": {
            "text/plain": [
              "'分开爹奔漫条她黑长吴王近'"
            ]
          },
          "metadata": {
            "tags": []
          },
          "execution_count": 11
        }
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "qS-Z3KZH4pXQ",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "def data_iter_random(corpus_indices,batch_size,num_steps):\n",
        "  #减1是因为输出的索引是相应输入的索引加1\n",
        "  num_examples=(len(corpus_indices)-1)//num_steps\n",
        "  epoch_size=num_examples//batch_size\n",
        "  example_indices=list(range(num_examples))\n",
        "  random.shuffle(example_indices)\n",
        "\n",
        "  #返回从pos开始的长为num_steps的序列\n",
        "  def _data(pos):\n",
        "    return corpus_indices[pos:pos+num_steps]\n",
        "\n",
        "  for i in range(epoch_size):\n",
        "    #每次读取batch_size个随机样本\n",
        "    i=i*batch_size\n",
        "    batch_indices=example_indices[i:i+batch_size]\n",
        "    X=[_data(j*num_steps) for j in batch_indices]\n",
        "    Y=[_data(j*num_steps+1) for j in batch_indices]\n",
        "    yield tf.constant(X),tf.constant(Y)"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "MtP5eC5r4vNw",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "def data_iter_consecutive(corpus_indices,batch_size,num_steps):\n",
        "  corpus_indices=tf.constant(corpus_indices)\n",
        "  data_len=len(corpus_indices)\n",
        "  batch_len=data_len//batch_size\n",
        "  indices=tf.reshape(corpus_indices[0:batch_size*batch_len],shape=(batch_size,batch_len))\n",
        "  epoch_size=(batch_len-1) // num_steps\n",
        "  for i in range(epoch_size):\n",
        "    i=i*num_steps\n",
        "    X=indices[:,i:i+num_steps]\n",
        "    Y=indices[:,i+1:i+num_steps+1]\n",
        "    yield X,Y"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "SviOVzbSuw7i",
        "colab_type": "text"
      },
      "source": [
        "###6.4.5. 裁剪梯度\n",
        "循环神经网络中较容易出现梯度衰减或梯度爆炸。我们会在“通过时间反向传播”一节中解释原因。为了应对梯度爆炸，我们可以裁剪梯度（clip gradient）。假设我们把所有模型参数梯度的元素拼接成一个向量  $g $，并设裁剪的阈值是 $\\theta$ 。裁剪后的梯度\n",
        "$$\n",
        "\\min\\left(\\frac{\\theta}{\\|\\boldsymbol{g}\\|}, 1\\right)\\boldsymbol{g}\n",
        "$$\n",
        "的 $L_2 $范数不超过 $\\theta$ 。"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "JzviW-nJWQl8",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "def sgd(params,l,t,lr,batch_size,theta):\n",
        "  norm=tf.constant([0],dtype=tf.float32)\n",
        "  for param in params:\n",
        "    dl_dp=t.gradient(l,param)\n",
        "    norm+=tf.reduce_sum((dl_dp**2))\n",
        "  norm=tf.sqrt(norm).numpy()\n",
        "  if norm>theta:\n",
        "    for param in params:\n",
        "      dl_dp=t.gradient(l,param) #求梯度\n",
        "      dl_dp=tf.assign(tf.Variable(dl_dp),dl_dp*theta/norm) #裁剪梯度\n",
        "      param.assign_sub(lr*dl_dp/batch_size) #更新梯度"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "8siry1vlvDP8",
        "colab_type": "text"
      },
      "source": [
        "###6.4.6. 困惑度\n",
        "我们通常使用困惑度（perplexity）来评价语言模型的好坏。回忆一下“softmax回归”一节中交叉熵损失函数的定义。困惑度是对交叉熵损失函数做指数运算后得到的值。特别地，\n",
        "\n",
        "* 最佳情况下，模型总是把标签类别的概率预测为1，此时困惑度为1；\n",
        "* 最坏情况下，模型总是把标签类别的概率预测为0，此时困惑度为正无穷；\n",
        "* 基线情况下，模型总是预测所有类别的概率都相同，此时困惑度为类别个数。\\\n",
        "显然，任何一个有效模型的困惑度必须小于类别个数。在本例中，困惑度必须小于词典大小vocab_size。"
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "WwIUgDjXvLus",
        "colab_type": "text"
      },
      "source": [
        "###6.4.7. 定义模型训练函数\n",
        "跟之前章节的模型训练函数相比，这里的模型训练函数有以下几点不同：\n",
        "\n",
        "* 使用困惑度评价模型。\n",
        "* 在迭代模型参数前裁剪梯度。\n",
        "* 对时序数据采用不同采样方法将导致隐藏状态初始化的不同。相关讨论可参考“语言模型数据集（周杰伦专辑歌词）”一节。\n",
        "另外，考虑到后面将介绍的其他循环神经网络，为了更通用，这里的函数实现更长一些。"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "GFoCg-i0uK3k",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "def train_and_predict_rnn(rnn,get_params,init_rnn_state,num_hiddens,vocab_size,corpus_indices,idx_to_char,char_to_idx,is_random_iter,num_epochs,\n",
        "                          num_steps,lr,clipping_theta,batch_size,pred_period,pred_len,prefixes):\n",
        "  if is_random_iter:\n",
        "    data_iter_fn=data_iter_random\n",
        "  else:\n",
        "    data_iter_fn=data_iter_consecutive\n",
        "\n",
        "  params=get_params()\n",
        "  loss=losses.SparseCategoricalCrossentropy(from_logits=True)\n",
        "\n",
        "  for epoch in range(num_epochs):\n",
        "    if not is_random_iter:#如果使用相邻采样，在epoch开始时初始化隐藏状态\n",
        "      state=init_rnn_state(batch_size,num_hiddens)\n",
        "    l_sum,n,start=0.0,0,time.time()\n",
        "    data_iter=data_iter_fn(corpus_indices,batch_size,num_steps)\n",
        "    for X,Y in data_iter:\n",
        "      if is_random_iter:#如果使用相邻采样，在每个小批量更新前初始化隐藏状态\n",
        "        state=init_rnn_state(batch_size,num_hiddens)\n",
        "      else:#否则需要使用detach函数从\n",
        "        state=tf.stop_gradient(state) #停止计算该张量的梯度\n",
        "      with tf.GradientTape(persistent=True) as t:\n",
        "        t.watch(params)\n",
        "        inputs=to_onehot(X,vocab_size)\n",
        "        # outputs有num_steps个形状为(batch_size, vocab_size)的矩阵\n",
        "        (outputs,state)=rnn(inputs,state,params)\n",
        "        # 拼接之后形状为(num_steps * batch_size, vocab_size)\n",
        "        outputs=tf.concat(values=[*outputs],axis=0)\n",
        "        # Y的形状是(batch_size, num_steps)，转置后再变成长度为\n",
        "        # batch * num_steps 的向量，这样跟输出的行一一对应\n",
        "        y=tf.reshape(tf.transpose(Y),shape=(-1,))\n",
        "        #使用交叉熵损失计算平均分类误差\n",
        "        l=tf.reduce_mean(loss(y,outputs))\n",
        "      sgd(params,l,t,lr,1,clipping_theta) #因为误差已经取过均值了,所以batch_size为1\n",
        "      l_sum+=l.numpy()*y.numpy().shape[0]\n",
        "      n+=y.numpy().shape[0]\n",
        "    if(epoch +1)%10==0:\n",
        "      print('epoch %d, perplexity %f, time %.2f sec' % (\n",
        "                epoch + 1, l_sum / n, time.time() - start))\n",
        "      for prefix in prefixes:\n",
        "          print(' -', predict_rnn(\n",
        "              prefix, pred_len, rnn, params, init_rnn_state,\n",
        "              num_hiddens, vocab_size, idx_to_char, char_to_idx))"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "jpuJAeUyv02b",
        "colab_type": "text"
      },
      "source": [
        "###6.4.8. 训练模型并创作歌词\n",
        "现在我们可以训练模型了。首先，设置模型超参数。我们将根据前缀“分开”和“不分开”分别创作长度为50个字符（不考虑前缀长度）的一段歌词。我们每过50个迭代周期便根据当前训练的模型创作一段歌词。"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "UqU-JdQHXMyt",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "num_epochs, num_steps, batch_size, lr, clipping_theta = 250, 35, 32, 1e2, 1e-2\n",
        "pred_period, pred_len, prefixes = 50, 50, ['分开', '不分开']"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "8hwiSx9cwFrq",
        "colab_type": "text"
      },
      "source": [
        "下面采用随机采样训练模型并创作歌词。"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "qzl9Eq3qXQPR",
        "colab_type": "code",
        "outputId": "7909682e-04b5-44f2-c717-dbcdc64e3e66",
        "colab": {
          "base_uri": "https://localhost:8080/",
          "height": 1000
        }
      },
      "source": [
        "train_and_predict_rnn(rnn,get_params,init_rnn_state,num_hiddens,vocab_size,corpus_indices,idx_to_char,char_to_idx,True,num_epochs,num_steps,lr,clipping_theta,batch_size,pred_period,pred_len,prefixes)"
      ],
      "execution_count": 0,
      "outputs": [
        {
          "output_type": "stream",
          "text": [
            "epoch 10, perplexity 5.707499, time 11.07 sec\n",
            " - 分开                                                  \n",
            " - 不分开                                                  \n",
            "epoch 20, perplexity 5.434195, time 10.88 sec\n",
            " - 分开 我 我 我 我 我 我 我 我 我 我 我 我 我 我 我 我 我 我 我 我 我 我 我 我 我\n",
            " - 不分开 我 我 我 我 我 我 我 我 我 我 我 我 我 我 我 我 我 我 我 我 我 我 我 我 我\n",
            "epoch 30, perplexity 5.042463, time 10.83 sec\n",
            " - 分开 我想 你不 我想你  我有你 我不 你想 我想你  我有你 我不 你想 我想你  我有你 我不 你\n",
            " - 不分开  哼 我有你 我不 你想你  我有你 我不 你想 我想你  我有你 我不 你想 我想你  我有你 \n",
            "epoch 40, perplexity 4.638070, time 10.75 sec\n",
            " - 分开 我想要 不要的让我疯狂的可爱女人 温坏的让我疯狂的可爱女人 温坏的让我疯狂的可爱女人 温坏的让我疯\n",
            " - 不分开 我想的让我 我不能 不爱的让我疯狂的可爱女人 温坏的让我疯狂的可爱女人 温坏的让我疯狂的可爱女人 \n",
            "epoch 50, perplexity 4.219021, time 10.73 sec\n",
            " - 分开 我想想这你 我不就 我不就的可爱女人 坏坏的让我疯狂的可爱女人 坏坏的让我疯狂的可爱女人 坏坏的让\n",
            " - 不分开 我想想 你爱我 我不就 我爱就的让爱女人 坏坏的让我疯狂的可爱女人 坏坏的让我疯狂的可爱女人 坏坏\n",
            "epoch 60, perplexity 3.818927, time 11.10 sec\n",
            " - 分开 我想要这生 我不 我不 我不 我不 我不 我不 我不 我不 我不 我不 我不 我不 我不 我不 我\n",
            " - 不分开  一样人 你是我有多头 有知的美栈  哼什么我 别你的外 你已完起 我有一定热 有一定美  有在一\n",
            "epoch 70, perplexity 3.414183, time 10.69 sec\n",
            " - 分开 娘子在我的溪据 我都要再有你  有 我想你这样  我有你这你很  什么我有你 你不着我不想 你知 \n",
            " - 不分开 我想想好生你 你知不觉 快使我有多功 后知不觉 如果我有多功 后知不觉 如果我有多功 后知不觉 如\n",
            "epoch 80, perplexity 2.996059, time 11.11 sec\n",
            " - 分开 我想要好生奏 我知我这多 是一直热柳 在人在人的  在情你的太 你一寄热现 让一定热现 有在在人前\n",
            " - 不分开  我去你 你爱我 别你 我不要 对情 这不球 有你安的让快 像都在人医 你在那人 在小村外的溪边 \n",
            "epoch 90, perplexity 2.644743, time 10.83 sec\n",
            " - 分开 我想想这样你 你知你这你 我不就你想你 你知 这不 我不 再不 我不 我不要再想你 不知不觉 你知\n",
            " - 不分开力 我爱你的爱你在西 想要你 说你我 娘你怎么 在人忆 别怪我 娘你的让我面狂的可爱女人 坏坏的让我\n",
            "epoch 100, perplexity 2.264596, time 10.82 sec\n",
            " - 分开 一颗用 一步两步三步四步望著天 看星星 一颗两步三颗四步 连成线背著背 我该念好生活 后知去 一直\n",
            " - 不分开 我给多好生活 后色我遇见你是一场悲剧 我想我这辈子注定一个人演戏 最后再一个人慢慢的窝 印地安飞斑\n",
            "epoch 110, perplexity 1.956210, time 10.77 sec\n",
            " - 分开 有杰在没有 有唱它 说弄堂 什么都著 在回忆 的片段 我 店没有 快沉默 一步四著三每日 我说耍的\n",
            " - 不分开吗 我爱你和 你想我妈 这人不  你怎么重 不有再  不知梦  不有梦重 没有再 一始的老我 我有你\n",
            "epoch 120, perplexity 1.701749, time 11.00 sec\n",
            " - 分开 我想想再 你知的让我面红的可爱女人 坏坏的让我疯狂的可爱女人 坏坏的让我疯狂的可爱女人 坏坏的让我\n",
            " - 不分开想 我爱你这 你想我有 这不了再 我想了带生我 不知不觉 我已了这节奏 后知后觉 我该好好生活 我不\n",
            "epoch 130, perplexity 1.448690, time 10.66 sec\n",
            " - 分开 默默在不过 你后懂美 其小村外的溪边 默默等待 娘子 一壶好酒 再杰在 的路段 有间些好慢 老街坊\n",
            " - 不分开吗 我爱你  你我的让 面怎的可 女人的老 一时 老念 征的完美主  没有你烦我有多难熬多打小  穿\n",
            "epoch 140, perplexity 1.236605, time 10.82 sec\n",
            " - 分开 有一在风霜 你在它里 在小村外的溪边河口默默 你却妈让我出糗 却小就耳濡目染 什么刀枪南棍棒 我都\n",
            " - 不分开吗 我叫你过 你打我有 这样跟容 一么海  如我抬起 你不再用  我的 我不红 爱属就没 想漠之 告\n",
            "epoch 150, perplexity 1.007246, time 10.95 sec\n",
            " - 分开 娘子我 别怪我 我想就这样牵着你的手不放开 爱可不能够简远单单没有悲害 你 靠着我的肩膀 你 在我\n",
            " - 不分开扫 我后你很 你打我有 这样不容 一句海动 的人笑 古如果囱在一句 我 你的手 别阵就老天快 我说 \n",
            "epoch 160, perplexity 0.858833, time 10.84 sec\n",
            " - 分开 我该在这样远 当不会很隐里 漂常抢不多 你是那里难喝 也伤妙 娘子她人在江南等望 脸化方到引力 也\n",
            " - 不分开吗 我叫你爸 你打我妈 这样跟容后个这样 别怪让笑险点缘B的心窗 看星斜你 在一着纵现 让慢蜡烛 温\n",
            "epoch 170, perplexity 0.735008, time 10.98 sec\n",
            " - 分开 一只在停留 谁在它停留的 为什么我女朋友场外加油 你却还让我出糗 却小就耳濡目染 什么刀枪跟棍棒 \n",
            " - 不分开期 我叫你爸 你打我妈 这样跟容 一句了人 我一定功宠 我的天空 是我去壁 是怎么想开 我也定你想 \n",
            "epoch 180, perplexity 0.603356, time 11.35 sec\n",
            " - 分开 装只在不因 几天都没有 有不懂著落 有不就到走 有窝在角落 三不懂 一烧惹 我打大这样牵着你的手不\n",
            " - 不分开期 我后你和 你想我妈 这样不到 一不放痛 我一定带我 走有是看 我也要难我 你的完美 在我心外婆 \n",
            "epoch 190, perplexity 0.518399, time 11.25 sec\n",
            " - 分开 装只在不因 几话就反难喝水为能活 脑袋瓜有一点秀逗 猎物死了它比谁都难过 印地安斑鸠 会学人开口 \n",
            " - 不分开期 我叫你爸 你打我妈 这样意义干嘛这样 别必让酒牵鼻子走 思念像底格里缘B般by  我的世界已狂风\n",
            "epoch 200, perplexity 0.453098, time 10.88 sec\n",
            " - 分开 默默在双截棍 哼哼哈兮 快使用双截棍 哼哼哈兮 如果我有轻功 飞檐走壁 为人耿直不屈 一身正气 快\n",
            " - 不分开扫 我叫你爸 你打我妈 这样对吗后嘛这样 我以女神之名 对你依依不舍 连隔壁邻居都猜 我满到伊斯坦堡\n",
            "epoch 210, perplexity 0.414169, time 10.79 sec\n",
            " - 分开 装子在在因 几分它停 全家怕日屋 白色蜡烛 温暖了空屋 白色蜡烛 温暖了空屋 白色蜡烛 温暖了空屋\n",
            " - 不分开简简的胖女巫 用拉丁文念咒语啦啦呜 她养的黑猫笑起来像哭 啦啦啦呜 都颗镇悬向  慢蜡着头球 景所拥\n",
            "epoch 220, perplexity 0.355845, time 10.80 sec\n",
            " - 分开 手只在美因中你听家  我  我 回爸去回棒 你说 看些 征不了弓难起 你 别想你口我妈攻 这样的手\n",
            " - 不分开吗把的胖女巫 用拉丁文念咒语啦啦呜 她养的黑猫笑起来像哭 啦啦啦呜 那止村双 染红夜空 过去种种 象\n",
            "epoch 230, perplexity 0.310077, time 10.82 sec\n",
            " - 分开 装蟑在美因 谁听的美丽 伤的完美主义 太彻底 让我连恨都难以下笔 将真心抽离写成日记 像是一场默剧\n",
            " - 不分开简 我叫你爸 你打我妈 这样对吗干嘛这样 何必让酒牵鼻子走 瞎 说底灌口默旁离像 他袋走了阳仪缘Ba\n",
            "epoch 240, perplexity 0.284974, time 10.70 sec\n",
            " - 分开 默默在在因向忧 什么我没细汉  我 你不休口幽快 想 我想 我不要烦恼你 不知 失想再考倒  不情\n",
            " - 不分开期 我叫你爸 你打我妈 这样对吗干嘛这样 何必让酒牵鼻子走 瞎 说底格口斯 去我伦苦的泥写 有古些对\n",
            "epoch 250, perplexity 0.278313, time 10.93 sec\n",
            " - 分开 装子在美因 谁色西没有 三分它 走在空 是属于明信片的铁盒里藏著一片玫瑰花瓣 黄金葛爬满了雕花的门\n",
            " - 不分开简 然后将过去 慢慢温习 让我爱上你 那场悲剧 是你完美演出的一场戏 宁愿心碎哭泣 再狠狠忘记 你爱\n"
          ],
          "name": "stdout"
        }
      ]
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "uhMlF7EjwbBb",
        "colab_type": "text"
      },
      "source": [
        "接下来采用相邻采样训练模型并创作歌词。"
      ]
    },
    {
      "cell_type": "code",
      "metadata": {
        "id": "9GUN9LWwNrOJ",
        "colab_type": "code",
        "colab": {}
      },
      "source": [
        "train_and_predict_rnn(rnn, get_params, init_rnn_state, num_hiddens,\n",
        "                      vocab_size, corpus_indices, idx_to_char,\n",
        "                      char_to_idx, False, num_epochs, num_steps, lr,\n",
        "                      clipping_theta, batch_size, pred_period, pred_len,\n",
        "                      prefixes)"
      ],
      "execution_count": 0,
      "outputs": []
    },
    {
      "cell_type": "markdown",
      "metadata": {
        "id": "SUCV15wfeVFd",
        "colab_type": "text"
      },
      "source": [
        "###6.4.9. 小结\n",
        "* 可以用基于字符级循环神经网络的语言模型来生成文本序列，例如创作歌词。\n",
        "* 当训练循环神经网络时，为了应对梯度爆炸，可以裁剪梯度。\n",
        "* 困惑度是对交叉熵损失函数做指数运算后得到的值。"
      ]
    }
  ]
}